/**
 * Java RTP Library (jlibrtp)
 * Copyright (C) 2006 Arne Kepp
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */
package com.detrum.sportcamera.jlibrtp;

/**
 * This is a four-directional data structures used for
 * the frame buffer, i.e. buffer for pkts that need
 * to be assimilated into complete frames.
 * 
 * All the actual work is done by PktBuffer.
 * 
 * @author Arne Kepp
 *
 */

public class FrameNode {

    static byte[] h264_start_seq = {0,0,0,1};

    /** The next node (RTP Timestamp), looking from the back -> next means older */
    protected FrameNode nextFrame = null;
    /** The previous node (RTP Timestmap), looking from the back -> prev means newer */
    protected FrameNode prevFrame = null;

    /** The next node within the frame, i.e. higher sequence number, same RTP timestamp */
    protected PktBufNode nextPktNode = null;
    /** The next node within the frame, i.e. lower sequence number, same RTP timestamp */
    protected PktBufNode prevPktNode = null;

    protected PktBufNode firstPktNode = null;

    /** Number of packets with the same RTP timestamp */
    protected int pktCount;
    /** The RTP timeStamp associated with this node */
    protected long timeStamp;
    protected int pt_type;
    protected int len;
	protected int est_len;

    protected boolean isComplete;

    private int min_seqnum;
    private int max_seqnum;
    private boolean recv_mark_flag;

    protected FrameNode(long timeStamp, int pt_type) {
        this.timeStamp = timeStamp;
        this.pktCount = 0;
        this.len = 0;
		this.est_len = 0;
        this.isComplete = false;
        this.pt_type = pt_type;

        min_seqnum = 65535;
        max_seqnum = 0;
        recv_mark_flag = false;

        nextPktNode = null;
        prevPktNode = null;
        firstPktNode = null;
    }

    private int update_frame_info(PktBufNode pkt){
        int seqnum = pkt.seqNum;

        if(seqnum<min_seqnum){
            min_seqnum = seqnum;
        }

        if(seqnum>max_seqnum) {
            max_seqnum = seqnum;
        }

        this.len += pkt.len;
		this.est_len +=  pkt.len + h264_start_seq.length + 1;
        this.pktCount ++ ;

        if(pkt.isMarked){
            recv_mark_flag = true;
        }

        if(recv_mark_flag) {
            frame_check();
        }

        if(isComplete){
            return 1;
        }

        return 0;
    }

    protected int addPktToFrame(PktBufNode pkt) {
        if (pkt.timeStamp != this.timeStamp) {
            return -2;
        }

        if (pktCount == 0) {
            this.nextPktNode = pkt;
            this.prevPktNode = pkt;

            return update_frame_info(pkt);
        }

        PktBufNode tmpnode = this.nextPktNode;
        if( pkt.seqNum > tmpnode.seqNum ) {
            //add last
            pkt.nextPktNode = tmpnode;
            tmpnode.prevPktNode = pkt;

            this.nextPktNode = pkt;

            return update_frame_info(pkt);
        }
        else {

            while( tmpnode!= null ) {
                if(tmpnode.seqNum > pkt.seqNum) {
                    tmpnode = tmpnode.nextPktNode;
                    continue;
                }

                if(tmpnode.seqNum == pkt.seqNum) {
                    return -1;
                }

                break;
            }

            if(tmpnode == null) {
                //add first
                pkt.nextPktNode = null;
                pkt.prevPktNode = this.prevPktNode;

                this.prevPktNode.nextPktNode = pkt;
                this.prevPktNode = pkt;
            }else {
                //add after tmpnode
                pkt.nextPktNode = tmpnode;
                pkt.prevPktNode = tmpnode.prevPktNode;

                if(tmpnode.prevPktNode !=null) {
                    tmpnode.prevPktNode.nextPktNode = pkt;
                }
                tmpnode.prevPktNode = pkt;
            }

            return update_frame_info(pkt);
        }
    }

    private int frame_check() {
        int last_seq = 0;
        int is_first_flag = 1;
        int diff_seq_score = 0;
        PktBufNode tmpnode=prevPktNode;

        if(pktCount == 0) {
            return -1;
        }

        while(tmpnode!=null) {
            if( ((last_seq + 1) & 0xFFFF) != tmpnode.seqNum ){
                if(is_first_flag == 0) {
                    diff_seq_score += 1;
                    firstPktNode = tmpnode;
                }
                is_first_flag = 0;
            }
            last_seq = tmpnode.seqNum;
            tmpnode = tmpnode.prevPktNode;
        }

        if(diff_seq_score==0){
            isComplete = true;
        }
        else if(diff_seq_score==1){
            if(min_seqnum == 0 && max_seqnum == 65535){
                isComplete = true;
            }
        }

        return 0;
    }

    private int getPktData(PktBufNode pkt, byte[] buf, int offset, int length){
        int copied_len = 0;

        int copy_offset = 0;
        int copy_len = 0;
        int nal = 0;
        int type = 0;

        int fu_a_start;
        byte temp1;
        byte temp2;

        nal = pkt.getByte(0);
        type = nal & 0x1F;

        if( type >= NALUType.NALU_TYPE_NON_IDR
                && type <= NALUType.NAL_TYPE_SINGLE_NAL_MAX ) {
            type = NALUType.NALU_TYPE_NON_IDR;
        }

        switch(type) {
            case NALUType.NALU_TYPE_UNSPECIFIED:
            case NALUType.NALU_TYPE_NON_IDR:
                System.arraycopy(h264_start_seq,0,buf,offset+copied_len,h264_start_seq.length);
                copied_len += h264_start_seq.length;

                copy_offset = 0;
                copy_len = pkt.len;
                break;

            case NALUType.NAL_TYPE_FU_A:
                fu_a_start = pkt.getByte(1);
                if( (fu_a_start & 0x80) == 0x80 ) {
                    System.arraycopy(h264_start_seq,0,buf,offset+copied_len,h264_start_seq.length);
                    copied_len += h264_start_seq.length;

                    temp1 = (byte)(pkt.getByte(0) & 0xE0) ;
                    temp2 = (byte)(pkt.getByte(1) & 0x1F) ;

                    buf[offset + copied_len] = (byte)(temp1|temp2);
                    copied_len ++ ;
                }
                copy_offset = 2;
                copy_len = pkt.len - 2;
                break;

            default:
                break;
        }

        pkt.getData(buf,offset+copied_len,copy_offset,copy_len);
        copied_len += copy_len;

        return copied_len;
    }

    public int getData(byte[] buf, int offset, int length){
        int cur_len = 0;
        PktBufNode pkt;

        if(isComplete != true){
            return 0;
        }

        if(min_seqnum == 0 && max_seqnum ==65535) {
            pkt = this.firstPktNode;
            while(pkt != null) {
                cur_len += getPktData(pkt, buf,offset+cur_len,length-cur_len);
                pkt=pkt.prevPktNode;
            }

            pkt = this.prevPktNode;
            while(pkt != this.firstPktNode) {
                cur_len += getPktData(pkt, buf,offset+cur_len,length-cur_len);
                pkt=pkt.prevPktNode;
            }
        } else{
            pkt = this.prevPktNode;
            while(pkt != null) {
                cur_len += getPktData(pkt, buf,offset+cur_len,length-cur_len);
                pkt=pkt.prevPktNode;
            }
        }

        return cur_len;
    }

    public long getTimeStamp(){return timeStamp;}
    public int getFrameLen(){return len;}
	public int getFrameEstLen(){return est_len;}
}